/*
 * Copyright 2024 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.pajamas.starter.integration.module.tx;

import org.apache.commons.lang3.StringUtils;
import org.pajamas.daydream.template.sequence.SequenceTemplate;
import org.pajamas.starter.integration.module.context.PajamasPropSrcConstants;
import org.pajamas.starter.integration.module.context.PajamasSequenceAutoConfiguration;
import org.pajamas.starter.integration.module.jpa.PajamasJpaAutoConfiguration;
import org.pajamas.starter.integration.spring.aop.AspectJAdvisorBuilder;
import org.springframework.aop.Advisor;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.interceptor.TransactionAttributeSource;
import org.springframework.transaction.interceptor.TransactionInterceptor;

import javax.sql.DataSource;

/**
 * @author william
 * @since 2024/1/1
 */
@ConditionalOnProperty(prefix = "org.pajamas.starter.tx", name = "enabled", havingValue = "true", matchIfMissing = true)
@EnableConfigurationProperties(PajamasTxProperties.class)
@EnableTransactionManagement(proxyTargetClass = true)
@AutoConfigureAfter({PajamasSequenceAutoConfiguration.class, PajamasJpaAutoConfiguration.class})
public class PajamasTxAutoConfiguration {
    private static final String SEQUENCE_PATTERN_ASPECTJ_EXPRESSION =
            "execution (* {" + PajamasPropSrcConstants.BASE_PACKAGE_APP + "}..service.*Service.*(..)) " +
                    "&& !@annotation(org.pajamas.starter.core.service.WithoutTx) " +
                    "&& @within(org.springframework.stereotype.Service)";

    @ConditionalOnBean(DataSource.class)
    @ConditionalOnProperty(prefix = "org.pajamas.starter.tx", name = "advisor", havingValue = "true",
            matchIfMissing = true)
    @ConditionalOnMissingBean(TxAttrSrcBuilder.class)
    @Bean
    public TxAttrSrcBuilder pajamasTxAttrSrcBuilder() {
        TxAttrSrcBuilder builder = TxAttrSrcBuilder.newBuilder();
        for (TxAttrSrcEnum txAttrSrcEnum : TxAttrSrcEnum.values()) {
            String[] matchNames = txAttrSrcEnum.getMatchNames();
            TxAttr attribute = txAttrSrcEnum.getAttribute();
            builder
                    .newTxAttr(matchNames)
                    .propagation(attribute.getPropagation())
                    .isolation(attribute.getIsolation())
                    .timeout(attribute.getTimeout())
                    .readOnly(attribute.getReadOnly())
                    .rollbackForClasses(attribute.getRollbackForClasses())
                    .noRollbackForClasses(attribute.getNoRollbackForClasses())
                    .build();
        }
        return builder;
    }

    @ConditionalOnBean(TxAttrSrcBuilder.class)
    @ConditionalOnMissingBean(name = "pajamasTransactionInterceptor")
    @Bean("pajamasTransactionInterceptor")
    public TransactionInterceptor pajamasTransactionInterceptor(TxAttrSrcBuilder builder,
                                                                ObjectProvider<TxAttrSrcBuilder.Customizer> customizers,
                                                                TransactionManager transactionManager
    ) {
        TransactionAttributeSource transactionAttributeSource =
                builder.customizers(customizers.orderedStream().iterator()).build();
        return new TransactionInterceptor(transactionManager, transactionAttributeSource);
    }


    @ConditionalOnBean(value = TransactionInterceptor.class, name = "pajamasTransactionInterceptor")
    @Bean
    public Advisor pajamasTxAdvisor(@Qualifier("pajamasTransactionInterceptor") TransactionInterceptor advice,
                                    PajamasTxProperties properties, SequenceTemplate sequenceTemplate) {
        String defaultAspectJExpression = sequenceTemplate.create(SEQUENCE_PATTERN_ASPECTJ_EXPRESSION).interpretSelf();
        return AspectJAdvisorBuilder.newBuilder()
                .pointcut(StringUtils.firstNonBlank(properties.getAspectjExpression(), defaultAspectJExpression))
                .advice(advice)
                .build();
    }
}
